package cn.wapache.time.trigger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.wapache.exception.Exceptions;
import cn.wapache.time.calendar.Calendar;
import cn.wapache.time.calendar.JdkCalendar;
import cn.wapache.timed.i18n.Si18n;

public class SimpleTrigger extends AbstractTrigger<SimpleTrigger>  {

    private static final long serialVersionUID = -3735980074222850397L;

    /**
     * <p>
     * Used to indicate the 'repeat count' of the trigger is indefinite. Or in
     * other words, the trigger should repeat continually until the trigger's
     * ending timestamp.
     * </p>
     */
    public static final int REPEAT_INDEFINITELY = -1;
    
    private static final int YEAR_TO_GIVEUP_SCHEDULING_AT = JdkCalendar.getYear(100);
    
	private static Logger logger = LoggerFactory.getLogger(SimpleTrigger.class);

	/** 
	 * 重复触发次数, 正常结束的话,应该触发repeatCount+1次。
	 * <p>
	 * 
	 * =0: 不重复执行
	 * -1: 无限循环
	 * >0: 重复有限次
	 */
    private int repeatCount = REPEAT_INDEFINITELY;

    private long repeatInterval = 0;

    private boolean complete = false;
    
    private long totalTimes;

    public SimpleTrigger(){
    	
    }

    public SimpleTrigger(String jobId){
    	this.setId(jobId+"-trigger");
    	this.setJobId(jobId);
    }
    
    ///////////////////////////////////////////////////////////////////////////
    //
    ///////////////////////////////////////////////////////////////////////////
    
    public int getRepeatCount() {
        return repeatCount;
    }
    public void setRepeatCount(int repeatCount) {
        if (repeatCount < 0 && repeatCount != REPEAT_INDEFINITELY) {
            throw new IllegalArgumentException(
                "Repeat count must be >= 0, use the constant REPEAT_INDEFINITELY for infinite.");
        }
        this.repeatCount = repeatCount;
    }

    /** the time interval (in milliseconds) */
    public long getRepeatInterval() {
        return repeatInterval;
    }
    public void setRepeatInterval(long repeatInterval) {
        if (repeatInterval <= 0) {
            throw new IllegalArgumentException("Repeat interval must be >= 0");
        }
        this.repeatInterval = repeatInterval;
    }
    
    /** Get the number of times the <code>SimpleTrigger</code> has already fired. */
    public int getTimesTriggered() {
        return timesTriggered;
    }
    public void setTimesTriggered(int timesTriggered) {
        this.timesTriggered = timesTriggered;
    }

	protected void updateState() {
		if(state == TriggerState.CREATE){
			return;
		}else if( nextFireTime < 0 ){
        	complete = true;
        	state = TriggerState.ERROR;
        }else if( nextFireTime==0 || (endTime > 0 && nextFireTime >= endTime) ){
        	complete = true;
        	state = TriggerState.COMPLETE;
        }
	}

    ///////////////////////////////////////////////////////////////////////////
    // operations
    ///////////////////////////////////////////////////////////////////////////

    @Override
    public long computeFirstFireTime(Calendar calendar) {
        
    	long firstFireTime = getStartTime();

        if (firstFireTime <=0 || calendar == null) {
            return System.currentTimeMillis();
        }
        
        while (firstFireTime >=0 && !calendar.isTimeIncluded(firstFireTime)) {
        	if ( (firstFireTime = getFireTimeAfter(firstFireTime)) <=0) { break; }
            if (JdkCalendar.getYear(firstFireTime) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
                return -1;
            }
        }

        if (firstFireTime == 0 ) {
        	firstFireTime = System.currentTimeMillis();
        }
        
        return firstFireTime;
    }

	// computeFireTimeBefore
    /**
     * <p>
     * Returns the last time at which the <code>SimpleTrigger</code> will
     * fire, before the given time. If the trigger will not fire before the
     * given time, <code>null</code> will be returned.
     * </p>
     */
    public long getFireTimeBefore(long end) {
        if (end < getStartTime()) {
            return 0;
        }

        int numFires = computeNumTimesFiredBetween(getStartTime(), end);

        return getStartTime() + (numFires * repeatInterval);
    }
    
	// computeFireTimeAfter
    @Override
    public long getFireTimeAfter(long afterTime) {

        if (afterTime <=0) {
            throw new IllegalArgumentException("argument <afterTime> must greater than zero.");
        }
        
        if (complete) {
            logger.trace("Trigger #{} get fire time after {}, trigger completed", getId(), afterTime);
            return 0; // trigger 已结束
        }

        if (repeatCount > 0 && timesTriggered > repeatCount ) {
            logger.trace("Trigger #{} get fire time after {}, triggered times {} > {}", getId(), afterTime, timesTriggered, repeatCount);
            return 0; // 触发次数已达到上限
        }

        if (repeatCount == 0 && afterTime >= startTime) {
            logger.trace("Trigger #{} get fire time after {}, no repeat and the time >= startTime {}", getId(), afterTime, startTime);
            return 0; // 不重复执行, 且 时间 大于起始时间?
        }

        long startMillis = startTime;
        long afterMillis = afterTime;
        long endMillis = (endTime <=0) ? Long.MAX_VALUE : endTime;

        if (endMillis <= afterMillis) {
            logger.trace("Trigger #{} get fire time after {}, the time >= endTime {}", getId(), afterTime, endMillis);
            return 0; // 参数时间 大于 终止时间
        }

        if (afterMillis < startMillis) {
            logger.trace("Trigger #{} get fire time after {}, return {}", getId(), afterTime, startMillis);
            return startMillis; // 参数时间 小于 起始时间, 那么下一次触发时间就是 起始时间
        }

        long numberOfTimesExecuted = ((afterMillis - startMillis) / repeatInterval) + 1;
        if ((numberOfTimesExecuted > repeatCount) && (repeatCount != REPEAT_INDEFINITELY)) {
            logger.trace("Trigger #{} get fire time after {}, repeat count {} > {}, startTime={}, diff={}, repeatInterval={}", 
            		getId(), afterTime, numberOfTimesExecuted, repeatCount, startMillis, (afterMillis - startMillis), repeatInterval);
            return 0;
        }

        long time = startMillis + (numberOfTimesExecuted * repeatInterval);

        if (endMillis <= time) {
            logger.trace("Trigger #{} get fire time after {}, the time {} >= endTime {}", getId(), time, endMillis);
            return 0;
        }

        return time;
    }

    public int computeNumTimesFiredBetween(long start, long end) {

        if(repeatInterval < 1) {
            return 0;
        }
        
        long time = end - start;

        return (int) (time / repeatInterval);
    }
    
    // computeFinalFireTime
    /**
     * <p>
     * Returns the final time at which the <code>SimpleTrigger</code> will
     * fire, if repeatCount is REPEAT_INDEFINITELY, null will be returned.
     * </p>
     * 
     * <p>
     * Note that the return time may be in the past.
     * </p>
     */
    @Override
    public long getFinalFireTime() {
        if (repeatCount == 0) {
            return startTime;
        }else if (repeatCount == REPEAT_INDEFINITELY) {
            return (endTime <=0) ? null : getFireTimeBefore(endTime); 
        }

        long lastTrigger = startTime + (repeatCount * repeatInterval);

        if ((endTime <=0) || (lastTrigger < endTime)) { 
            return lastTrigger;
        } else {
            return getFireTimeBefore(endTime);
        }
    }
    
    ///////////////////////////////////////////////////////////////////////////
    // event handlers
    ///////////////////////////////////////////////////////////////////////////

    // onTriggered
    @Override
    public void triggered(Calendar calendar) {
    	
    	totalTimes++;
    	
    	// 更新触发时间
        timesTriggered++;
        previousFireTime = nextFireTime;
        nextFireTime = getFireTimeAfter(nextFireTime);
        
        // 更新状态
        updateState();
        
        logger.debug("Trigger #{} triggered, Previous={}, Next={}, Interval={}, state={}, times={}, totalTimes={}", 
        	getId(), previousFireTime, nextFireTime, (nextFireTime - previousFireTime), state, timesTriggered, totalTimes);
        
    }

    // onMisfire
    @Override
    public boolean updateAfterMisfire(Calendar cal) {
    	
    	MisfirePolicy instr = getMisfireInstruction();
    	
    	// 第一轮,根据当前指令和各种属性计算出真实的指令
		switch(instr){
		case IGNORE:
			// 不做修正
        	// 跟日历无关, 跟 计划触发次数(repeatCount) 无关
			
			// 因为从不更新 原始的起始时间 和已执行次数, 所以每次计算下一次的时间是按 
			// 理论已执行次数 = 初步计算的下一次时间-原始的起始时间/执行周期
			// 如果 理论已执行次数 已经达到 计划执行次数, 则下次时间置为0
			// 否则 下一次执行时间 = 理论已执行次数 * 执行周期 + 起始时间
			
			// 典型例子, 设置起始时间为 2017-03-18 18:11:00, 执行次数为 10 次, 周期为 1分钟
			// 如果在 2017-03-18 18:20:00 才开启服务, 那么实际就只会执行 2 次
			// 如果在 2017-03-18 18:21:01 才开启服务, 那么实际一次都不会执行
			break;
		case SMART:
			// 根据 计划触发次数(repeatCount) 的值 决定 采用 什么策略
			
			// 如果没有设置, 马上触发
			// 如果无限次, 那么就使用以日历为主的调整策略
			// 如果有限次, 那么就使用以重复次数为主的调整策略
			
			// 因为默认是无限次, 所以 默认会使用RESCHEDULE_NEXT_WITH_REMAINING_COUNT策略
			
            if (getRepeatCount() == 0) {
                instr = MisfirePolicy.FIRE_NOW;
            } else if (getRepeatCount() == REPEAT_INDEFINITELY) {
                instr = MisfirePolicy.RESCHEDULE_NEXT_WITH_REMAINING_COUNT;
            } else {
                instr = MisfirePolicy.RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;
            }
            break;
		default:
			break;
    	}

        logger.trace("Trigger #{} misfire instruction is {}", getId(), instr);
        
        long oldNextFireTime = this.getNextFireTime();
        long oldTimesTriggered = this.getTimesTriggered();
        long oldStartTime = this.getStartTime();
        long oldRepeatCount = this.getRepeatCount();
        
		// 第二轮, 根据真实指令设置各种属性
		switch(instr){
		case IGNORE:
			return false;
		case FIRE_NOW:{ 
        	// 不过错过了多少次, 都是现在马上触发一次
        	// 跟日历无关, 跟 计划触发次数(repeatCount) 无关
            setNextFireTime(System.currentTimeMillis());
            break;
        }case FIRE_ONCE_NOW:
        	// 不过错过了多少次, 都是现在马上触发一次
        	Exceptions.illegalState(Si18n.trigger_misfire_instruction_is_invalid, instr);
			break;
		case RESCHEDULE_NEXT_WITH_EXISTING_COUNT:{
        	// 错过了的次数就当作它已经执行过
        	// 跟日历有关, 跟 计划触发次数(repeatCount) 无关
			
        	// 重置 下一次计划触发时间 为当前时间后的第一个日历范围内时间
			
            long newFireTime = getFireTimeAfter(System.currentTimeMillis());
            while (newFireTime > 0 && cal != null && !cal.isTimeIncluded(newFireTime)) {
	        	newFireTime = cal.getNextIncludedTime(newFireTime) - 1;
                newFireTime = getFireTimeAfter(newFireTime);

                if(newFireTime <=0) { break; }
                
                if (JdkCalendar.getYear(newFireTime) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
                    newFireTime = -1;
                }
            }
            setNextFireTime(newFireTime);
            break;
        }case RESCHEDULE_NEXT_WITH_REMAINING_COUNT:{
        	// 错过了的次数就当作它已经执行过
        	// 跟日历有关, 跟 计划触发次数(repeatCount) 无关
        	
        	// 更新 已触发次数为 = 已触发次数 + 错过了的次数
        	// 重置 下一次计划触发时间 为当前时间后的第一个日历范围内时间
        	
            long newFireTime = getFireTimeAfter(System.currentTimeMillis());
            while (newFireTime > 0 && cal != null && !cal.isTimeIncluded(newFireTime)) {
	        	newFireTime = cal.getNextIncludedTime(newFireTime) - 1;
                newFireTime = getFireTimeAfter(newFireTime);

                if(newFireTime <=0) { break; }

                if (JdkCalendar.getYear(newFireTime) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
                    newFireTime = -1;
                }
            }
            
            if (newFireTime >0) {
                int timesMissed = computeNumTimesFiredBetween(nextFireTime, newFireTime);
                setTimesTriggered(getTimesTriggered() + timesMissed);
            }

            setNextFireTime(newFireTime);
            break;
        }case RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT:{
        	// 错过了的次数要重新执行
        	// 跟日历无关, 跟 计划触发次数(repeatCount) 有关

        	// 1. 没有设置 计划触发次数(repeatCount)
        	//    重置 起始时间(startTime) 和 计划下次触发时间(NextFireTime) 为当前时间
        	
        	// 2. 有设置 计划触发次数(repeatCount)
        	// 2.1 设置为无限次
        	//    处理逻辑同 1
        	// 2.2 设置为有限次
        	//    重置计划触发次数为  = 剩余触发次数 = 计划触发次数 - 已经触发过的次数
        	//    重置已触发次数为0次
        	//    重置 起始时间(startTime) 和 计划下次触发时间(NextFireTime) 为当前时间
        	//    其实已经相当于重新生成一个新触发器了
        	
            long newFireTime = System.currentTimeMillis();
            if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) {
                setRepeatCount(getRepeatCount() - getTimesTriggered());
                setTimesTriggered(0);
            }
            
            if (getEndTime() >0 && getEndTime()<(newFireTime)) {
                setNextFireTime(0); // We are past the end time
            } else {
                setStartTime(newFireTime);
                setNextFireTime(newFireTime); // 以最快的速度将错过了的次数给补回来
            } 
            break;
        }case RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT:{
        	// 错过了的次数就让它错过了, 不重新执行了
        	// 跟日历无关, 跟 计划触发次数(repeatCount) 有关
        	
        	// 1. 没有设置 计划触发次数(repeatCount)
        	//    重置 起始时间(startTime) 和 计划下次触发时间(NextFireTime) 为当前时间
        	
        	// 2. 有设置 计划触发次数(repeatCount)
        	// 2.1 设置为无限次
        	//    处理逻辑同 1
        	// 2.2 设置为有限次
        	//    重置计划触发次数为  = 剩余触发次数 = 计划触发次数 - 已经触发过的次数 - 错过了的次数
        	//    重置 起始时间(startTime) 和 计划下次触发时间(NextFireTime) 为当前时间
        	//    重置已触发次数为0次
        	//    其实已经相当于重新生成一个新触发器了
        	
            long newFireTime = System.currentTimeMillis();

            if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) {
            	int timesMissed = computeNumTimesFiredBetween(nextFireTime, newFireTime);
                int remainingCount = getRepeatCount() - (getTimesTriggered() + timesMissed);
                if (remainingCount <= 0) { 
                    remainingCount = 0;
                }
                setRepeatCount(remainingCount);
                setTimesTriggered(0);
            }

            if (getEndTime() >0 && getEndTime()<(newFireTime)) {
                setNextFireTime(0); // We are past the end time
            } else {
                setStartTime(newFireTime);
                setNextFireTime(newFireTime);
            }
            break;
        }
        default:
        	Si18n.newException(Si18n.enum_switch_miss_some_items, instr);
		}
        
		if(oldStartTime != startTime || oldNextFireTime != nextFireTime 
			|| oldTimesTriggered != timesTriggered || oldRepeatCount != repeatCount)
		{
	        Trigger.TriggerState oldState = state;
	        
	        updateState();
			
			// 有修正
	        if(logger.isDebugEnabled()){
	        	
	        	StringBuilder buf = new StringBuilder("Trigger #"+getId()+" misfire updated");

	        	if(oldState != state){
	        		buf.append(", state=").append(oldState).append("->").append(state);
	        	}
	        	if(oldStartTime != startTime){
	        		buf.append(", startTime=").append(oldStartTime).append("->").append(startTime);
	        	}
	        	if(oldNextFireTime != nextFireTime){
	        		buf.append(", nextFireTime=").append(oldNextFireTime).append("->").append(nextFireTime);
	        	}
	        	if(oldTimesTriggered != timesTriggered){
	        		buf.append(", timesTriggered=").append(oldTimesTriggered).append("->").append(timesTriggered);
	        	}
	        	if(oldRepeatCount != repeatCount){
	        		buf.append(", repeatCount=").append(oldRepeatCount).append("->").append(repeatCount).append("");
	        	}

	        	logger.debug(buf.toString());
	        }
	        
	        return true;
		}
		return false;
    }    

    // onCalendarChanged
    @Override
    public void updateWithNewCalendar(Calendar calendar, long misfireThreshold)
    {
    	
        nextFireTime = getFireTimeAfter(previousFireTime);

        if (nextFireTime <=0 || calendar == null) {
            return;
        }
        
    	this.setCalendarId(calendar.getId());
        
        long now = System.currentTimeMillis();
        while (nextFireTime >0 && !calendar.isTimeIncluded(nextFireTime)) {

            if( (nextFireTime = getFireTimeAfter(nextFireTime)) <=0) { break; }
            
            if (JdkCalendar.getYear(nextFireTime) > YEAR_TO_GIVEUP_SCHEDULING_AT) {
            	//avoid infinite loop
                nextFireTime = -1;
            }

            if( nextFireTime > 0 && nextFireTime < now ) {
                if(now - nextFireTime >= misfireThreshold) {
                    nextFireTime = getFireTimeAfter(nextFireTime);
                }
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // others
    ///////////////////////////////////////////////////////////////////////////
    
    /**
     * <p>
     * Determines whether or not the <code>SimpleTrigger</code> will occur
     * again.
     * </p>
     */
    @Override
    public boolean mayFireAgain() {
        return (getNextFireTime() > 0);
    }

    /**
     * <p>
     * Validates whether the properties of the <code>JobDetail</code> are
     * valid for submission into a <code>Scheduler</code>.
     * 
     * @throws IllegalStateException
     *           if a required property (such as Name, Group, Class) is not
     *           set.
     */
    @Override
    public void validate() {
        super.validate();

        if (repeatCount != 0 && repeatInterval < 1) {
            throw new RuntimeException("Repeat Interval cannot be zero.");
        }
    }

    @Override
    protected boolean validateMisfireInstruction(Trigger.MisfirePolicy misfireInstruction) {
    	switch(misfireInstruction){
		case FIRE_NOW:
			break;
		case FIRE_ONCE_NOW:
			break;
		case IGNORE:
			break;
		case RESCHEDULE_NEXT_WITH_EXISTING_COUNT:
			break;
		case RESCHEDULE_NEXT_WITH_REMAINING_COUNT:
			break;
		case RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT:
			break;
		case RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT:
			break;
		case SMART:
			break;
		case DO_NOTHING:
			break;
		default:
			break;
    	}
    	return false;
    }
}
